背景

最近有个项目模块,支持 100 路并发的视频分享预览,需进行测试覆盖。

找 100 个设备同时登录不太可能,即使在 PC 上用模拟器,按照之前的经验,性能比较好的 PC 一台也就能跑 10 个左右,凑不出那么多空闲 PC 用于测试。于是想到用脚本模拟 APP 从登录到预览的整个流程,只拉流不解码,对性能要求比较低,理论上一台 PC 就能实现。

申了一圈文档权限,发现从文档出发整理整个流程还是比较困难,所以决定先用实际 APP 抓包,再结合文档整理接口流程。

代理

说到抓包可能最先想到的就是 Wireshark,诚然用 Wireshark 配置无线抓包、或是在设备上直接跑 tcpdump 再导出成抓包文件用 Wireshark 打开,是可以抓到所有 APP 发送和接受的请求的。但问题在于,这部分请求是 HTTPS 加密的,我们无法直接看到交互内容,此方法也就行不通。

HTTPS 的数据包虽然能抓到,但无法查看内容

移动端抓包工具基本采用的都是中间人攻击的方式(MITM),简单来说就是在服务器和客户端中间做代理,截获两边发往对端的数据。

比较有名的抓包工具,例如 Fiddler、Charles 等,可以对 APP 进行抓包,其原理是抓包功能工具开一个 HTTP 代理,将手机与 PC 连入同一网络,配置手机的代理为工具的代理端口,就可以实现中间人攻击抓包。

但是,我司的物联 APP 并不使用安卓系统的 HTTP 代理,而是在 APP 内部自建 Client 直接与服务器建立 SSL 连接进行交互,所以此时即使配置了代理,流量也不会走到代理工具上去,也就抓不到包。

VPN

直接的 HTTP 代理行不通,还有 VPN 的方式,通过该方式在客户端与抓包工具之间的 VPN 连接,实现所有流量经过抓包工具,从而进行拦截。

安卓端可以直接用 HttpCanary 来自建 VPN 服务器进行抓包;PC 端也有类似的工具,推荐 HTTP Toolkit,界面友好功能强大。

certificate pinning

无论是代理还是 VPN,想对 HTTPS 的加密数据进行解析,都必须先在客户端系统上安装由抓包工具提供的虚假 CA 证书。

客户端在发送 HTTPS 请求时,系统会先对服务器发来的证书进行校验,由于已经在系统上安装了抓包工具提供的证书,所以校验自然能通过。

然而我们的物联 APP,并不适用系统的证书对服务器发来的证书进行校验,而是用了称为 certificate pinning 的方法,使用内置的自签证书来对服务器证书进行校验,此时校验失败,客户端并不信任抓包工具发来的证书。结果就是客户端根本不会把数据发给我们的抓包工具,在 APP 上表现就是直接提示“网络错误”。

可以看到手机的其他数据包都能抓到,但 TP-LINK 物联 APP 相关的请求未发送出去。

Frida

那么有没有解决方案呢,肯定是有的,可以使⽤例如 xposed 框架 + justtrustme 模块或 VirtualApp 等⼯具对证书验证的函数进⾏ hook,使其在证书校验时始终返回成功,即可绕过证书验证。但是按照其提供的参考链接中的方法,没能直接 hook 到校验证书的 API。

在 HTTP Toolkit 的官网,推荐是使用 Frida 工具绕过 certificate pinning 的验证,原理基本相同。此部分还需要对手机进行 root,使用 adb 连接手机(root 有风险,操作需谨慎,我是找了一个不用的旧手机)。

但是我按照步骤安装配置完,并行不通。

研发提到app是在native层使⽤openssl做的证书验证,所以justtrustme没有hook到,这时我大概明白了,证书验证的部分并没有写在 java 源码里,所以 hook 不出来,于是我就去搜索“APP native层openssl证书验证”,没想到居然找到一篇对我司安防 APP 进行抓包的博客:逆向破解 | 利用Frida手动绕过Android APP证书校验

他同样是用 Frida 工具进行 hook,我直接 copy 了他的 js 脚本:

Java.perform(function(){
	var nativePointer = Module.findExportByName("libIPCAppContextJNI.so", "X509_verify_cert");
	console.log("-------------Start-------------");
	Interceptor.attach(nativePointer, {
		onEnter: function(args){
		// 此处是修改入参要做的逻辑,在这里不需要修改,留空即可
		},
		onLeave: function(retval){
			console.log("----------leaving-------------")
			// 打印原始的返回值
			console.log(retval);
			// replace修改返回值
			retval.replace(0x1)
			// 再次打印出来验证一下修改是否成功
			console.log(retval);
		}
	});
});

由于有注释,理解起来也不难,就是在需要对证书进行校验时,让它直接返回校验成功。

跑了一遍,直接报错expect a pointer,在 Frida 中直接执行Module.findExportByName("libIPCAppContextJNI.so", "X509_verify_cert")返回了null,感觉应该是 APP 版本更新修改了一些依赖库。

再找了一些其他人写的用 Frida 绕过 certificate pinning 验证的 js 脚本进行参考,把上面的脚本修改成:

Java.perform(function() {
    const System = Java.use("java.lang.System");
    const Runtime = Java.use("java.lang.Runtime");
    const SystemLoad_2 = System.loadLibrary.overload("java.lang.String");
    const VMStack = Java.use("dalvik.system.VMStack");

    SystemLoad_2.implementation = function(library) {
        console.log("Loading dynamic library => " + library);
        try {
            const loaded = Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), library);
            // here your hook
            Interceptor.attach(Module.findExportByName("lib"+library+".so", "X509_verify_cert"), {
                onEnter: function(args) {
                    // 不做任何事
                },
                onLeave: function(retval){
                	// 如果校验失败
                    if(retval == 0x0) {
                        console.log(library);
                        // replace修改返回值
                        retval.replace(0x1);
                        // 再次打印出来验证一下修改是否成功
                        console.log(retval);
                    }
                }
            });
            return loaded;
        } catch(ex) {
            console.log(ex);
        }
    };
});

就是在所有库调用时,都判断证书校验部分的返回值,如果返回校验失败就手动改成校验成功,用此方法,终于是抓到了我们的 APP 发出去的包,然而:

看到 Status 鲜红的 502,我的心又凉了半截。再一看右边的说明,是服务器的 HTTPS 证书未被信任,这可太熟悉了,和我们的 VMS 系统首次访问浏览器提示”你的连接不是专用连接“一个道理,忽略就行了,只可惜这个工具让我掏钱”Get Pro“版本才能继续。

本来想着反正已经能抓到 APP 发出去的请求了,看不到响应也问题不大,但此时发现 APP 卡死了,过了一会直接闪退,想了一下应该是发出去的请求都没有收到响应,可能就卡在某一步了,依赖前面的响应的请求,后面也没法发,看来工作还得继续。

既然我已经用 Frida 在客户端上实现了绕过证书验证,那说明抓包工具应该已经能实现抓包了,只不过是 HTTP Toolkit 这个中间人不给我转发,我拿不到响应。于是又想到 HttpCanary。

在 HttpCanary 上一试,还是没抓到,但是有很多 Warnning,点开 Warning:

解决方案是用 Xposed 模块 JustTrustMe 解除证书固定,这部分我已经用 Frida 做了,那么我导入该自签证书应该就可以了吧:

终于,想要的结果出现了:

总结

  • APP 的 HTTPS 抓包常用 Fiddler、Charles 等工具,但我司物联 APP 不走系统代理,则抓不到包
  • 用 HttpCanary、HTTP Toolkit 等可以自建 VPN 进行流量拦截抓包,但我司物联 APP 用了 certificate pinning,使用自签证书进行校验,导致校验失败抓不到包
  • 使用 xposed + justtrustme 或 Frida 等工具对证书验证的函数进⾏hook,使其始终返回成功可绕过证书验证,但我司物联 APP 的证书验证并未写在 Java 层,导致 hook 不到
  • Frida 可直接对调用的库进行 hook,使用此方法可成功绕过证书验证
  • HTTP Toolkit 工具免费版对于证书未信任的服务器不会发送请求
  • HttpCanary 在绕过证书验证后可发送请求并接受响应,能正常抓包

至此,在折腾了一大番之后,完成了想要抓个包的小目标。

只能说学无止境,对不了解的领域还是要多探索积累。

参考

https://httptoolkit.tech/docs/guides/android/

https://httptoolkit.tech/blog/frida-certificate-pinning/

http://www.52bug.cn/cracktool/6380.html

https://stackoverflow.com/questions/60794720/frida-hook-native-method-failure-on-android-q